package com.lyd.sweet.web.handler;

import com.lyd.sweet.exception.SweetBasicException;
import com.lyd.sweet.model.ResponseObject;
import com.lyd.sweet.model.ServiceStatus;
import com.lyd.sweet.web.properties.SweetHandlerProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.*;

/**
 * Rest服务统一、通用的异常处理
 * 针对引用此的rest微服务抛出的所有异常统一在此处理，统一在此记录日志。
 * 使用@RestControllerAdvice注解，针对RestController ，Response Content-Type值统一为application/json;charset=UTF-8
 * http请求的状态码统一返回：400. HttpStatus.BAD_REQUEST
 * 原则上RestController代码只负责记录好异常信息抛出合适的异常，不需要考虑异常日志的记录。
 * 对于异常发生后业务代码要进行补救处理的（如：重试，回滚等特殊处理），这类异常应该在业务代码中处理，不在本类中处理。
 * <p>
 * 具体异常处理设计原则：
 * 1、所有异常都会在本类中捕获。
 * 2、如果RestController中的方法已经被执行到了，所有产生的异常我们认为是中台的业务异常，统一封装成ResponseObject抛出并记录日志。
 * 3、RestController中的方法被执行之前产生的异常，统一交给Spring框架或web服务中间件去进行原生的处理。
 * 比如404 405等各类HTTP异常。如果个别有需要处理，单独捕获进行处理。
 *
 * @author 木木
 **/
@RestControllerAdvice
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 异常时日志是否显示详细请求内容
     */
    private final Boolean showRequestDetailWhenErr;

    public GlobalExceptionHandler(SweetHandlerProperties handlerProperties) {
        this.showRequestDetailWhenErr = handlerProperties.isResultHandlerEnabled();
    }

    /**
     * ServletException通用处理
     * 所有http请求的各类异常都继承于ServletException。这里捕获后统一不做处理，直接抛出，交给Spring和web容器去处理。
     *
     * @param e
     * @return
     * @throws ServletException
     */
    @ExceptionHandler(ServletException.class)
    public ResponseObject ServletExceptionHandle(ServletException e) throws ServletException {
        throw e;
    }

    /**
     * Exception 异常统一处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResponseObject exceptionHandler(Exception e) {
        logger.error("异常处理程序-未知异常!{},{}", e.getMessage(), getReqMessage(), e);
        return ResponseObject.fail(ServiceStatus.FAIL);
    }

    /**
     * RuntimeException 运行时异常统一处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseObject runtimeExceptionHandler(RuntimeException e) {
        logger.error("异常处理程序-未知运行时异常!{},{}", e.getMessage(), getReqMessage(), e);
        return ResponseObject.fail(ServiceStatus.FAIL);
    }

    /**
     * 服务参数异常的统一处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public ResponseObject methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException e) {
        String paramsFailMsg = this.getParamsFailMsg(e.getBindingResult());
        logger.error("异常处理程序-服务方法参数校验异常!{},{}", paramsFailMsg, getReqMessage(), e);
        return ResponseObject.fail(String.format("服务方法参数校验异常%s", paramsFailMsg));
    }

    /**
     * 服务参数异常的统一处理（表单方式处理）
     *
     * @param result
     * @return
     */
    @ExceptionHandler(BindException.class)
    public ResponseObject methodArgumentNotValidExceptionHandle(BindingResult result) {
        String paramsFailMsg = this.getParamsFailMsg(result);
        logger.error("异常处理程序-服务方法参数校验异常!{},{}", paramsFailMsg, getReqMessage());
        return ResponseObject.fail(String.format("服务方法参数校验异常%s", paramsFailMsg));
    }

    /**
     * 请求参数缺失
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseObject methodArgumentNotValidExceptionHandle(MissingServletRequestParameterException e) {
        logger.error("异常处理程序-缺少请求参数!{}", getReqMessage(), e);
        return ResponseObject.fail("缺少请求参数");
    }

    /**
     * 类型转换异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(ClassCastException.class)
    public ResponseObject classCastExceptionHandler(ClassCastException ex) {
        logger.error("异常处理程序-类型转换异常!{}", getReqMessage(), ex);
        return ResponseObject.fail("类型转换异常");
    }

    /**
     * IO异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(IOException.class)
    public ResponseObject iOExceptionHandler(IOException ex) {
        logger.error("异常处理程序-IO异常!{}", getReqMessage(), ex);
        return ResponseObject.fail("IO异常");
    }

    /**
     * 数组越界异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public ResponseObject indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
        logger.error("异常处理程序-数组越界异常!{}", getReqMessage(), ex);
        return ResponseObject.fail("数组越界异常");
    }

    /**
     * 方法参数类型不匹配异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseObject methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
        logger.error("异常处理程序-方法参数类型不匹配异常!{}", getReqMessage());
        return ResponseObject.fail("方法参数类型不匹配异常");
    }

    /**
     * 自定义根异常的默认处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(SweetBasicException.class)
    public ResponseObject sweetBaseExceptionHandler(SweetBasicException e) {
        logger.error("异常处理程序-自定义异常!{}", getReqMessage(), e);
        //对于自定义异常，如果有传入message则使用，没有则使用ServiceStatus对应的描述
        String message = (e.getMessage() != null && !e.getMessage().equals("")) ? e.getMessage() : e.getServiceStatus().getDesc();
        return ResponseObject.fail(e.getServiceStatus(), message);
    }

    /**
     * sql异常的通用处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(SQLException.class)
    public ResponseObject sqlExceptionHandle(SQLException e) {
        logger.error("异常处理程序-SQL处理异常!{}", getReqMessage(), e);
        return ResponseObject.fail("SQL处理异常");
    }

    /**
     * 解析错误的通用处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ParseException.class)
    public ResponseObject parseExceptionHandle(ParseException e) {
        logger.error("异常处理程序-方法解析异常!{}", getReqMessage(), e);
        return ResponseObject.fail("方法解析异常");
    }

    /**
     * 空指针异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    public ResponseObject nullPointerExceptionHandler(NullPointerException e) {
        logger.error("异常处理程序-空指针异常!{}", e.getMessage(), e);
        return ResponseObject.fail("空指针异常");
    }

    /**
     * 找不到方法异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(NoSuchMethodException.class)
    public ResponseObject noSuchMethodExceptionHandler(NoSuchMethodException ex) {
        logger.error("异常处理程序-找不到方法异常!{}", getReqMessage(), ex);
        return ResponseObject.fail("找不到方法异常");
    }

    /**
     * 获取api请求详细
     *
     * @return
     */
    private String getReqMessage() {
        if (showRequestDetailWhenErr) {
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (ObjectUtils.isEmpty(servletRequestAttributes)) {
                return "";
            }
            HttpServletRequest request = servletRequestAttributes.getRequest();
            String uri = request.getRequestURI();
            String showFormat = "service run err, uri is : [%s], params is : [%s]";
            Map<String, String[]> params = request.getParameterMap();
            Map<String, String> showRes = new HashMap<>(16);
            params.forEach((key, value) -> {
                String valueStr = value == null || value.length == 0 ?
                        "" : String.join(",", value);
                showRes.put(key, valueStr);
            });
            String paramsStr = showRes.toString();
            return String.format(showFormat, uri, paramsStr);
        }
        return "";
    }

    /**
     * 获取参数异常信息
     *
     * @param bindingResult
     * @return
     */
    private String getParamsFailMsg(BindingResult bindingResult) {
        if (!ObjectUtils.isEmpty(bindingResult)
                && !bindingResult.getAllErrors().isEmpty()) {
            List<String> list = new ArrayList<>(8);
            for (ObjectError error : bindingResult.getAllErrors()) {
                String msg = error.getDefaultMessage();
                list.add(msg);
            }
            return CollectionUtils.isEmpty(list) ? "" : Arrays.toString(list.toArray());
        }
        return "";
    }
}
